#!/usr/bin/env python
# Originally modified from A. Israel's script seen at
# https://gist.github.com/aisrael/b2b78d9dfdd176a232b9
"""To run this script first install the dependencies


  virtualenv v
  source v/bin/activate
  pip install githubpy GitPython requests

Generate a github access token; this is needed as the anonymous access
to Github's API will easily hit the limit even with a single invocation.
For details see:
https://help.github.com/articles/creating-an-access-token-for-command-line-use/

Next either set the github token as an env variable
`GITHUB_ACCESS_TOKEN` or alternatively invoke the script with
`--token` switch.

Example:

  ceph-release-notes -r tags/v0.87..origin/giant $(git rev-parse --show-toplevel)

"""

from __future__ import print_function
import argparse
import github
import os
import re
import sys
import requests

from git import Repo


merge_re = re.compile("Merge pull request #(\d+).*")
fixes_re = re.compile(r"Fixes\:? #(\d+)")
tracker_re = re.compile("http://tracker.ceph.com/issues/(\d+)")
signed_off_re = re.compile("Signed-off-by: (.+) <")
tracker_uri = "http://tracker.ceph.com/issues/{0}.json"

def get_original_issue(issue, verbose):
    r = requests.get(tracker_uri.format(issue),params={"include":"relations"}).json()

    # looking up for the original issue only makes sense
    # when dealing with an issue in the Backport tracker
    if r["issue"]["tracker"]["name"] != "Backport":
        if verbose:
            print ("http://tracker.ceph.com/issues/" + issue + " is from the tracker " + r["issue"]["tracker"]["name"] + ", do not look for the original issue")
        return issue

    # if a Backport issue does not have a relation, keep it
    if "relations" not in r["issue"]:
        if verbose:
            print ("http://tracker.ceph.com/issues/" + issue + " has no relations, do not look for the original issue")
        return issue
    
    copied_to = [str(i['issue_id']) for i in r["issue"]["relations"] if i["relation_type"] == "copied_to"]
    if copied_to:
        if len(copied_to) > 1:
            if verbose:
                print ("ERROR: http://tracker.ceph.com/issues/" + issue + " has more than one Copied To relation")
            return issue
        if verbose:
            print ("http://tracker.ceph.com/issues/" + issue + " is the backport of http://tracker.ceph.com/issues/" + copied_to[0])
        return copied_to[0]
    else:
        if verbose:
            print ("http://tracker.ceph.com/issues/" + issue + " has no copied_to relations, do not look for the original issue")
        return issue


def make_release_notes(gh, repo, ref, plaintext, verbose, strict):

    issue2prs = {}
    pr2issues = {}
    pr2info = {}

    for commit in repo.iter_commits(ref, merges=True):
        merge = merge_re.match(commit.summary)
        if merge:
            number = merge.group(1)
            pr = gh.repos("ceph")("ceph").pulls(number).get()
            message_lines = commit.message.split('\n')
            if len(message_lines) > 2 and message_lines[2].strip() != pr['title'].strip():
                lines = []
                for line in message_lines[2:]:
                    if 'Reviewed-by' in line:
                        continue
                    lines.append(line.strip())
                message = "\n".join(lines)
            else:
                message = None
            issues = []
            if pr['body']:
                issues = fixes_re.findall(pr['body']) + tracker_re.findall(pr['body'])

            authors = {}
            for c in repo.iter_commits("{sha1}^1..{sha1}^2".format(sha1=commit.hexsha)):
                for author in re.findall("Signed-off-by:\s*(.*?)\s*<", c.message):
                    authors[author] = 1
                issues.extend(fixes_re.findall(c.message) + tracker_re.findall(c.message))
            if authors:
                author = ", ".join(authors.keys())
            else:
                author = commit.parents[-1].author.name

            if strict and not issues:
                print ("ERROR: http://github.com/ceph/ceph/pull/" + str(number) + " has no associated issue")
                continue    
            
            title = pr['title']

            if strict:
                title_re = '^(cli|common|mon|osd|fs|librbd|rbd|fs|mds|objecter|rgw|build/ops|tests|tools|doc|crush|librados):'
                if not re.match(title_re, title):
                    print ("ERROR: http://github.com/ceph/ceph/pull/" + str(number) + " title " + title + " does not match " + title_re)

            pr2info[number] = (author, title, message)

            for issue in set(issues):
                issue = get_original_issue(issue, verbose)
                issue2prs.setdefault(issue, []).append(number)
                pr2issues.setdefault(number, []).append(issue)
                sys.stdout.write('.')

    print (" done collecting merges.")

    if strict:
        for (issue, prs) in issue2prs.iteritems():
            if len(prs) > 1:
                print (">>>>>>> " + str(len(prs)) + " pr for issue " + issue)

    for (pr, (author, title, message)) in sorted(pr2info.iteritems(), key=lambda (k,v): v[1]):
        if pr in pr2issues:
            if plaintext:
                issues = map(lambda issue: '#' + str(issue), pr2issues[pr])
            else:
                issues = map(lambda issue: '`issue#{issue} <http://tracker.ceph.com/issues/{issue}>`_'.format(issue=issue), pr2issues[pr])
            issues = ", ".join(issues) + ", "
        else:
            issues = ''
        if plaintext:
            print (u"* {title} ({issues}{author})".format(title=title, issues=issues, author=author))
        else:
            print (u"* {title} ({issues}`pr#{pr} <http://github.com/ceph/ceph/pull/{pr}>`_, {author})".format(title=title, issues=issues, author=author, pr=pr))
        if message:
            print (message)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--rev", "-r",
                        help="git revision range for creating release notes")
    parser.add_argument("--text", "-t",
                        action='store_true', default=None,
                        help="output plain text only, no links")
    parser.add_argument("--verbose", "-v",
                        action='store_true', default=None,
                        help="verbose")
    parser.add_argument("--strict",
                        action='store_true', default=None,
                        help="strict")
    parser.add_argument("repo", metavar="repo",
                        help="path to ceph git repo")
    parser.add_argument("--token", default=os.getenv("GITHUB_ACCESS_TOKEN"),
                        help="Github Access Token ($GITHUB_ACCESS_TOKEN otherwise)")

    args = parser.parse_args()
    gh = github.GitHub(
        access_token=args.token)

    make_release_notes(gh, Repo(args.repo), args.rev, args.text, args.verbose, args.strict)
